#include "Auxiliar.h"

typedef struct {
    int socket;
    int nivel;
    char nomeHost[15];
    int vetRecebido[NUM_CLIENTES_NIVEL_1 * TAM_VET];

} atributosDaThread;

//ordena os vetores recebidos das threads a partir dos clientes e realiza ordenacao baseada no Mergesort
void copiarOrdenado(int *vet, int *vetRecebido1, int *vetRecebido2, int tamVetRecebido) {

    int i, a = 0, b = 0;

     //obtem-se o vetor ordenado 'vet' com os valores de vetRecebido1 e vetRecebido2
    for (i = 0; i < 2 * tamVetRecebido && a < tamVetRecebido && b < tamVetRecebido; i++) {

        if (vetRecebido1[a] < vetRecebido2[b])
            vet[i] = vetRecebido1[a++];

        else
            vet[i] = vetRecebido2[b++];
    }

    //completa o vetor 'vet' com um dos vetRecebido, caso o outro ja tenha acabado de ser inserido em 'vet'
    while (a < tamVetRecebido) {

        vet[i++] = vetRecebido1[a++];
    }

    while (b < tamVetRecebido) {

        vet[i++] = vetRecebido1[b++];
    }
}

//a comunicação é análoga a leitura e escrita em arquivos
void receber(void *atributos) { 

    char buffer[256];
    int tamLido = 0, k, bufferVet[NUM_CLIENTES_NIVEL_1 * TAM_VET], j;

    //escreve a confirmacao de recebimento no buffer que sera encaminhado ao cliente quando o vetor de dados do cliente chegar
    strcpy(buffer, " @");
    strcat(buffer, ((atributosDaThread*) atributos)->nomeHost);
    strcat(buffer, ": Vetor parcial recebido.");

    //le o vetor de dados transmitido pelo cliente, armazena temporariamente em bufferVet e depois em vetRecebido, do servidor
    while (tamLido < (int) pow(2, (((atributosDaThread*) atributos)->nivel - 2)) * TAM_VET * sizeof (int)) {

        k = read(((atributosDaThread*) atributos)->socket, bufferVet, ((int) pow(2, (((atributosDaThread*) atributos)->nivel - 2)) * TAM_VET - tamLido) * sizeof (int));

        if (k <= 0) {

            printf("\n\n >> %s: Erro ao ler do socket.", ((atributosDaThread*) atributos)->nomeHost);
            close(((atributosDaThread*) atributos)->socket);
            exit(1);

        } else {

            for (j = 0; j < k / sizeof (int); j++) {

                ((atributosDaThread*) atributos)->vetRecebido[tamLido / sizeof (int) +j] = bufferVet[j];
            }
        }

        tamLido += k;
    }

    //escreve no buffer a confirmacao do recebimento do vetor de dados
    if (write(((atributosDaThread*) atributos)->socket, buffer, strlen(buffer)) < 0) {

        printf("\n\n >> %s: Erro ao enviar resposta.", ((atributosDaThread*) atributos)->nomeHost);
        close(((atributosDaThread*) atributos)->socket);
        exit(1);
    }

    //fecha o socket e encerra a thread
    close(((atributosDaThread*) atributos)->socket);
    pthread_exit(NULL);
}

//a comunicação é análoga a leitura e escrita em arquivos
void enviar(void *atributos) { 

    char buffer[256];
    struct timespec t1, t2;

    //captura o tempo 't1' do inicio do envio
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &t1);

    //envia para o servidor de nivel superior vetRecebido, que agora possui os dados ordenados (o comparativo verifica se todo o vetor foi transferido)
    if (write(((atributosDaThread*) atributos)->socket, ((atributosDaThread*) atributos)->vetRecebido, NUM_CLIENTES_ATENDIDOS * (int) pow(2, (((atributosDaThread*) atributos)->nivel - 2)) * TAM_VET * sizeof (int)) < NUM_CLIENTES_ATENDIDOS * (int) pow(2, (((atributosDaThread*) atributos)->nivel - 2)) * TAM_VET * sizeof (int)) {

        printf("\n\n >> %s: Erro ao escrever no socket.", ((atributosDaThread*) atributos)->nomeHost);
        close(((atributosDaThread*) atributos)->socket);
        exit(1);

    } else {

        bzero(buffer, 256);

        //se o envio foi feito, le o buffer para obter a resposta do servidor
        if (read(((atributosDaThread*) atributos)->socket, buffer, 255) < 0) {

            printf("\n\n >> %s: Erro ao ler do socket", ((atributosDaThread*) atributos)->nomeHost);
            close(((atributosDaThread*) atributos)->socket);
            exit(1);

        } else {

            //captura o tempo 't2' do fim do envio e calcula a diferenca entre os dois. obtendo, portanto, o tempo total de envio
            clock_gettime(CLOCK_THREAD_CPUTIME_ID, &t2);
            printf("\n\n >> %s: Resposta do servidor: %s", ((atributosDaThread*) atributos)->nomeHost, buffer);
            printf("\n\n >> %s: Tempo de comunicacao: %ld s e %ld ns", ((atributosDaThread*) atributos)->nomeHost, (long int) diferencaEntreInstantes(t1, t2).tv_sec, (long int) diferencaEntreInstantes(t1, t2).tv_nsec);
        }

        fflush(stdout);
    }

    //fecha o socket e encerra a thread
    close(((atributosDaThread*) atributos)->socket);
    pthread_exit(NULL);
}

int main(int argc, char** argv) {

    int socketEspera, indThread = 0, *vet, nivel, i, j; 
    struct timespec t1, t2;
    char nomeHost[15];
    socklen_t tamEndCliente; //tamanho do endereço do cliente
    struct sockaddr_in endServidor, endCliente; //a estrutura do tipo sockaddr_in é a peça fundamental para a especificação dos parâmetros e endereços da comunicação. Seus campos ficaram claros mais abaixo
    pthread_t tabelaDeThreads[argc], tabelaDeThreadsEnvio[argc];
    atributosDaThread tabelaDeAtributos[argc], tabelaDeAtributosEnvio[argc];

    gethostname(nomeHost, 15);

    printf("\n\n ******* Servidor - %s *******", nomeHost);

    nivel = atoi(argv[1]);

    tamEndCliente = sizeof (endCliente);

    //AF_INET: conexão pela internet; SOCK_STREAM:comunicação sera por um fluxo de dados contínuo; 0 significa o uso protocolo padrao (definido pelo SO)
    if ((socketEspera = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 
    
        printf("\n\n >> %s: Erro ao abrir o socket.", nomeHost);
        exit(1);
    }

    //bzero() inicializa os campos com o valor do caracter nulo '\0', para evitar problemas com lixo
    bzero((char *) &endServidor, sizeof (endServidor)); //bzero() simplesmente inicializa os campos da estrutura com o valor do caracter nulo '\0', somente para evitar problemas com lixo nas variáveis

    endServidor.sin_family = AF_INET; //AF_INET: enderecos serao do padrao da internet
    endServidor.sin_port = htons(NUM_PORTA_INICIAL + nivel); ///htons() converte o número da porta para o formato (big endian) usado nas redes
    endServidor.sin_addr.s_addr = INADDR_ANY; //INADDR_ANY: servidor aceitara requisicoes de clientes com qualquer endereço

    //serve para associar o socket a porta escolhida, com os parametros especificados
    if (bind(socketEspera, (struct sockaddr *) &endServidor, sizeof (endServidor)) < 0) { //serve para "prender" o socket à porta escolhida, com os parâmetros especificados

        printf("\n\n >> %s: Erro ao associar o socket ao endereco.", nomeHost);
        close(socketEspera);
        exit(1);
    }

    //faz com que o servidor "ouca" constantemente o socket a espera de requisições; o 5 é o numero maximo de requisicoes que poderao esperar na fila de atendimento
    if (listen(socketEspera, 5) == -1) { //faz com que o servidor "ouça" constantemente o socket à espera de requisições; o 5 é o número máximo de requisições que poderão esperar na fila de atendimento

        printf("\n\n >> %s: Erro ao ouvir o estado de conexao do socket.", nomeHost);
        close(socketEspera);
        exit(1);
    }

    printf("\n\n >> %s: Esperando por requisicoes na porta %d. \n", nomeHost, NUM_PORTA_INICIAL + nivel);
    fflush(stdout);

    while (indThread < NUM_CLIENTES_ATENDIDOS) {

        //accept() [abaixo] faz com que o servidor aceite pedidos de conexao realizados na porta associada a socketEspera; enquanto nenhuma requisicao é feita, o servidor fica bloqueado
        //Quando uma requisição é aceita, um novo socket é criado para a comunicacao, com descritor tabelaDeAtributos[indThread].socket
        tabelaDeAtributos[indThread].socket = accept(socketEspera, (struct sockaddr *) &endCliente, &tamEndCliente); // O argumento endCliente será preenchido através da chamada à função connect() no código do cliente.  

        if (tabelaDeAtributos[indThread].socket < 0) {

            printf("\n\n >> %s: Erro ao aceitar conexao.", nomeHost);
            close(socketEspera);
            exit(1);
        }

        //tabelaDeAtributos[indThread].buffer = (int*) malloc((nivel - 1) * TAM_VET * sizeof (int));
        //armazena nos atributos da thread o nivel ao qual esse servidor pertence e o nome do mesmo
        tabelaDeAtributos[indThread].nivel = nivel;
        strcpy(tabelaDeAtributos[indThread].nomeHost, nomeHost);

        //inicia a thread
        pthread_create(&tabelaDeThreads[indThread], NULL, (void*) &receber, (void*) &tabelaDeAtributos[indThread]);
        indThread++;
    }

    i = 0;

    while (i < indThread) {

        //faz com que se espere pelo encerramento de todas as threads
        if (pthread_join(tabelaDeThreads[i], NULL) != 0) {

            printf("\n\n >> %s: Erro ao receber retorno da thread.", nomeHost);
            close(socketEspera);
            exit(1);
        }

        i++;
    }

    //captura o tempo 't1' no inicio da ordenacao
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &t1);

    //aloca dinamicamente um vetor de acordo com o nivel do servidor.  N1 = 125, N2 = 250, N3 = 500, N4 = 1000 posicoes.
    vet = (int*) malloc(NUM_CLIENTES_ATENDIDOS * (int) pow(2, (nivel - 2)) * TAM_VET * sizeof (int));

    //ordena os vetores recebidos das threads a partir dos clientes
    copiarOrdenado(vet, tabelaDeAtributos[0].vetRecebido, tabelaDeAtributos[1].vetRecebido, (int) pow(2, (nivel - 2)) * TAM_VET);

    //captura o tempo 't2' no fim na ordenacao
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &t2);

    //calcula o tempo total de ordenacao baseado nos tempos 't1' e 't2'
    printf("\n\n >> %s: Tempo de ordenacao: %ld s e %ld ns", nomeHost, (long int) diferencaEntreInstantes(t1, t2).tv_sec, (long int) diferencaEntreInstantes(t1, t2).tv_nsec);

    //caso seja o servidor de maior nivel
    if (nivel == NUM_NIVEIS) {

        printf("\n\n >> %s: Vetor total ordenado: ", nomeHost);

        for (i = 0; i < NUM_CLIENTES_ATENDIDOS * (int) pow(2, (nivel - 2)) * TAM_VET; i++) {

            printf("%d ", vet[i]);
        }
    }

    close(socketEspera);

    // ********    Cliente   *******************************************************
    //Cada servidor de niveis 2, 3 e 4 comportam-se momentaneamente como clientes quando enviam o vetor ao proximo nivel (N4 envia os vetores a 8 hosts em N1)

    indThread = 0;

    while (indThread < (argc - 2)) {

        tabelaDeAtributosEnvio[indThread].socket = socket(AF_INET, SOCK_STREAM, 0);

        //tabelaDeAtributosEnvio[indThread].buffer = (int*) malloc(NUM_CLIENTES * (int) pow(2, (nivel - 2)) * TAM_VET * sizeof (int));
        strcpy(tabelaDeAtributosEnvio[indThread].nomeHost, nomeHost);

        //armazena em vetRecebido o 'vet', proveniente da ordenacao dos dados recebidos anteriormente
        for (i = 0; i < NUM_CLIENTES_ATENDIDOS * (int) pow(2, (nivel - 2)) * TAM_VET; i++) {

            tabelaDeAtributosEnvio[indThread].vetRecebido[i] = vet[i];
        }

        //armazena o nivel do servidor atual (que nesse trecho atuara como cliente, pois sera ele quem enviara os dados)
        tabelaDeAtributosEnvio[indThread].nivel = nivel;

        //"zera" o vetor endServidor
        bzero((char *) &endServidor, sizeof (endServidor));

        //analogo a parte do servidor
        inet_aton(argv[indThread + 2], &endServidor.sin_addr);
        endServidor.sin_family = AF_INET; //também análogo ao código do servidor
        endServidor.sin_port = htons(NUM_PORTA_INICIAL + nivel % NUM_NIVEIS + 1);

        //connect() faz a requisição de conexão ao servidor especificado; o servidor deverá estar esperando, através da função accept()
        //tenta estabelecer uma comunicacao com o servidor de nivel superior; tendo como argumentos (descritor de socket, endereco e porta do servidor, tamanho do endereco)
        if (connect(tabelaDeAtributosEnvio[indThread].socket, (struct sockaddr *) &endServidor, sizeof (endServidor)) < 0) { 

            printf("\n\n >> %s: Erro ao estabelecer conexao.", nomeHost);
            close(tabelaDeAtributosEnvio[indThread].socket);
            exit(1);

        } else {

            //cria-se thread para o envio do vetor ao servidor de nivel superior
            pthread_create(&tabelaDeThreadsEnvio[indThread], NULL, (void*) &enviar, (void*) &tabelaDeAtributosEnvio[indThread]);
        }

        indThread++;
    }

    i = 0;

    while (i < indThread) {

        //faz com que se espere pelo encerramento de todas as threads
        if (pthread_join(tabelaDeThreadsEnvio[i], NULL) != 0) {

            printf("\n\n >> %s: Erro ao aguardar o encerramento da thread.", nomeHost);
            exit(1);
        }
        
        i++;
    }

    free((void*) vet);
    
    printf("\n\n");

    return (0);
}